<?php
/**
 * REST Request 
 * @package dd_aws
 */

/**
 * REST Request
 * @package dd_aws
 */
class dd_aws_RestRequest {

    /**
     * Access Key ID
     * @var string
     */
    private $accessKey;

    /**
     * Secret Access Key
     * @var string
     */
    private $secretAccessKey;

    /**
     * Verb
     * @var string
     */
    private $verb;

    /**
     * Hostname
     * @var string
     */
    private $hostname;

    /**
     * Resource
     * @var string
     */
    private $resource;

    /**
     * Headers
     * @var array
     */
    private $headers;

    /**
     * Amazon headers
     * @var array
     */
    private $amzHeaders;

    /**
     * Curl options
     * @var array
     */
    private $curlOpts;

    /**
     * Use SSL?
     * @var bool
     */
    private $useSsl;

    /**
     * Contains response data
     * @var bool
     */
    private $response;

    /**
     * Constructor
     * @param string $accessKey Access Key ID
     * @param string $secretAccessKey Secret Access Key
     * @param string $verb Verb
     * @param string $hostname Hostname
     * @param string $resource Resource
     * @param array $headers Associative array of headers
     * @param array $amzHeaders Associative array of Amazon headers
     * @param array $curlOpts Associative array of curl options
     * @param bool $useSsl Use SSL?
     */
    public function __construct($accessKey, $secretAccessKey, $verb, $hostname, $resource, $headers = null, $amzHeaders = null, $curlOpts = null, $useSsl = false) {
        $this->accessKey = $accessKey;
        $this->secretAccessKey = $secretAccessKey;
        $this->verb = $verb;
        $this->hostname = $hostname;
        $this->resource = $resource;
        $this->headers = is_array($headers) ? $headers : array();
        $this->amzHeaders = is_array($amzHeaders) ? $amzHeaders : array();
        $this->curlOpts = is_array($curlOpts) ? $curlOpts : array();
        $this->useSsl = $useSsl;
    }

    /**
     * Prepare the REST request
     */
    protected function prepare() {
    }

    /**
     * Generate authentication header
     * @return string authentication header
     */
    protected function generateAuthenticationHeader() {
        return 'NONE';
    }

    /**
     * Execute REST request
     */
    final public function execute() {

        $this->setHeader('Host', $this->hostname());

        $this->prepare();

        $url = (($this->useSsl() && extension_loaded('openssl')) ?
                'https://':'http://').$this->hostname().$this->resource();

        //
        // IMPORTANT NOTE:
        //
        // The setCurlOpt commands here will not take effect if the
        // the option has already been set! This may have a varying
        // impact on the code here. Hopefully this will be a good
        // thing and not a bad thing...
        //

        $this->setCurlOpt(CURLOPT_URL, $url);

        $this->setCurlOpt(CURLOPT_USERAGENT, 'dd-aws/rest-request');

        $headers = array();

        foreach ( $this->amzHeaders as $header => $value ) {
            $headers[] = $header . ': ' . $value;
        }

        foreach ( $this->headers as $header => $value ) {
            $headers[] = $header . ': ' . $value;
        }

        $headers[] = 'Authorization: ' . $this->generateAuthenticationHeader();

        $this->setCurlOpt(CURLOPT_HTTPHEADER, $headers);

        if ( $this->useSsl() and false ) {
            $this->setCurlOpt(CURLOPT_SSL_VERIFYHOST, 1);
            $this->setCurlOpt(CURLOPT_SSL_VERIFYPEER, 1);
        }

        $this->setCurlOpt(CURLOPT_FOLLOWLOCATION, true);

        $this->setCurlOpt(CURLOPT_CUSTOMREQUEST, $this->verb());

        if ( $this->verb() == 'PUT' ) {
            $this->setCurlOpt(CURLOPT_PUT, true);
        }

        // This call in particular may fail depending on the
        // nature of the specific request as some calls may
        // want to send data to a filehandle instead.
        $this->setCurlOpt(CURLOPT_RETURNTRANSFER, true);

        $this->setCurlOpt(CURLOPT_HEADERFUNCTION, array(
            $this,
            'defaultResponseHeaderCallback'
        ));

        $curl = curl_init();

        $this->setCurlOptions($curl);

        // Setup our response.
        $this->response = array(
            'rawHeaders' => array(),
        );

        // Execute our curl request.
        $this->response['body'] = curl_exec($curl);

        // Get some additional information.
        $this->response['curl_info'] = curl_getinfo($curl);
        $this->response['curl_errno'] = curl_errno($curl);
        $this->response['curl_error'] = curl_error($curl);

        curl_close($curl);

        return $this->response;

    }

    /**
     * Access Key ID
     * @return string
     */
    final protected function accessKey() { return $this->accessKey; }


    /**
     * Secret Access Key
     * @return string
     */
    final protected function secretAccessKey() { return $this->secretAccessKey; }


    /**
     * Verb
     * @return string
     */
    final protected function verb() { return $this->verb; }

    /**
     * Hostname
     * @return string
     */
    final protected function hostname() { return $this->hostname; }

    /**
     * Resource
     * @return string
     */
    final protected function resource() { return $this->resource; }

    /**
     * Headers
     * @return array
     */
    final protected function headers() { return $this->headers; }

    /**
     * Amazon headers
     * @return array
     */
    final protected function amzHeaders() { return $this->amzHeaders; }

    /**
     * Set a header
     * @param string $name Name of header
     * @param string $value Value
     */
    final protected function setHeader($name, $value) {
        $this->headers[$name] = $value;
    }

    /**
     * Get a header
     * @param string $name Name of header
     * @return string
     */
    final protected function getHeader($name) {
        return array_key_exists($name, $this->headers) ? $this->headers[$name] : null;
    }

    /**
     * Set an Amazon header
     * @param string $name Name of header
     * @param string $value Value
     */
    final protected function setAmzHeader($name, $value) {
        $this->amzHeaders[$name] = $value;
    }

    /**
     * Get an Amazon header
     * @param string $name Name of header
     * @return string
     */
    final protected function getAmzHeader($name) {
        return $this->amzHeaders[$name];
    }

    /**
     * Set a curl option
     * @param string $option Option
     * @param string $value Value
     * @param bool $overwrite Overwrite option if already set?
     */
    final protected function setCurlOpt($option, $value, $overwrite = false) {

        if ( ( ! array_key_exists($option, $this->curlOpts) ) or $overwrite ) {
            // If this option has not already been set or we have been
            // instructed to overwrite options, set this option to
            // the specified value.
            $this->curlOpts[$option] = $value;
        }

    }

    /**
     * Use SSL?
     * @return bool
     */
    final protected function useSsl() { return $this->useSsl; }

    /**
     * Set curl options
     * @param curl $curl curl handle
     */
    private function setCurlOptions($curl) {
        foreach ( $this->curlOpts as $option => $value ) {
            curl_setopt($curl, $option, $value);
        }
    }

    /**
     * Generate an AWS hash
     * @return string
     */
    protected function generateAwsHash($string) {
        // Algorithm adapted (stolen) from:
        // http://code.google.com/p/php-aws/
        // ...who adapted (stole) from:
        // http://pear.php.net/package/Crypt_HMAC/
        return base64_encode(extension_loaded('hash') ?
            hash_hmac('sha1', $string, $this->secretAccessKey(), true) : pack('H*', sha1(
            (str_pad($this->secretAccessKey(), 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
            pack('H*', sha1((str_pad($this->secretAccessKey(), 64, chr(0x00)) ^
            (str_repeat(chr(0x36), 64))) . $string)))));
    }

    /**
     * Default response header callback
     * @param $curl handle
     * @param $data header data
     */
    private function defaultResponseHeaderCallback($curl, $data) {
        if (($strlen = strlen($data)) <= 2) return $strlen;
        if (substr($data, 0, 4) == 'HTTP') {
            $this->response['code'] = (int)substr($data, 9, 3);
        } else {
            list($header, $value) = explode(': ', trim($data), 2);
            $this->response['rawHeaders'][$header] = $value;
        }
        return $strlen;
    }

}

?>
